Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic fft interface #8

Merged
merged 27 commits into from
Jul 5, 2021
Merged

Generic fft interface #8

merged 27 commits into from
Jul 5, 2021

Conversation

ckormanyos
Copy link
Member

No description provided.

@ckormanyos
Copy link
Member Author

Hi @Lagrang3 this is a nice development and this PR is a good step forward.

I am slightly concerned with this line and its follow-up lines. Basically, there is later in the file a default template parameter relying on the BSL FFT backend. This is syntactically OK. But if the situation were changed from the point of FFTW, then there would be a BSL template file having a dependence on FFTW.

Now having a dependence on BSL header-only DFT-backend is OK. But I wonder if this hints at a slight problem in class/template granularity somewhere such that the main FFT header file is, in fact, dependent on at least one of the backends?

A forward template class declaration is not enough because the BSL dft is actually subsequently used as a base class in further class declarations, so all of the FFT's require the BSL backend.

I don't know, off the top of my head how to resolve this or if it is even worth resolving?

Let's consider this.

@ckormanyos
Copy link
Member Author

I found, prior to creating this PR, use of or. But some compilers don't handle this. So, in addition to some MSVC level 3 warnings, i also replaced the or with traditional || operator.

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jun 26, 2021

Hi @Lagrang3 this is a nice development and this PR is a good step forward.

I am slightly concerned with this line and its follow-up lines. Basically, there is later in the file a default template parameter relying on the BSL FFT backend. This is syntactically OK. But if the situation were changed from the point of FFTW, then there would be a BSL template file having a dependence on FFTW.

Now having a dependence on BSL header-only DFT-backend is OK. But I wonder if this hints at a slight problem in class/template granularity somewhere such that the main FFT header file is, in fact, dependent on at least one of the backends?

A forward template class declaration is not enough because the BSL dft is actually subsequently used as a base class in further class declarations, so all of the FFT's require the BSL backend.

I don't know, off the top of my head how to resolve this or if it is even worth resolving?

Let's consider this.

Hi @ckormanyos , could you elaborate on this? We could remove the depedency of the main header from the BSL backend by just removing the line and removing the default backend. The FFTW backend does not depend on the BSL backend.

@ckormanyos
Copy link
Member Author

ckormanyos commented Jun 26, 2021

... forward template class declaration is not enough

could you elaborate on this? We could remove the depedency of the main header from the BSL backend by just removing the [line] and removing the default backend ...

Of course.

You are right. I had tried to remove the include line from the header. But then, I had absentmindedly forgotten to include the backend-type header in my test code.

So let's please do this in my opinion. The main header should not contain the backend headers. If this means that the client always has to specify the type of backend in the template instantiation, then i can live with that.

@Lagrang3 : Thoughts?

@ckormanyos
Copy link
Member Author

In my commit "Try CI w/out BSL default but design still not right", it's still not quite right.

I want it to be analogous to Boost.Multiprecision. In that one, inclusion of the backend type automatically includes the boilerplace number code. Here we would want the same. I select BSL, FFTW, GSL, then include merely its backend header and I'm good to go. That's how it should be.

@ckormanyos
Copy link
Member Author

OK. This got green again.

I do, however, believe that the symbiosis between the backend/bolierplate headers can be improved.

This exercise revealed a bunch of probs with MSVC on the prototype code (mostly gone now). So this motivates me to get cracking on #7 (to be started soon...).

@Lagrang3
Copy link
Collaborator

In my commit "Try CI w/out BSL default but design still not right", it's still not quite right.

I want it to be analogous to Boost.Multiprecision. In that one, inclusion of the backend type automatically includes the boilerplace number code. Here we would want the same. I select BSL, FFTW, GSL, then include merely its backend header and I'm good to go. That's how it should be.

I'll see what I can do.

@ckormanyos
Copy link
Member Author

ckormanyos commented Jun 27, 2021

select BSL, FFTW, GSL, then include merely its backend header and I'm good to go

I'll see what I can do.

Two program artifacts come to mind, either a namespace localization or a static-polymorphic template struct or class which serves as nothing but a holder for the FFT functions and the type of the underlying FFT object.

Consider the latter, a static-polymorphic template struct or class.

If we were to define a template struct or class class, as follows:

template<BackendFftType>
struct fft_holder
{
  static void forward(...);
};

We could replace the line:

boost::math::fft::dft_forward<boost::math::fft::bsl_dft>(...)

With the call to the static function forward in the template holder struct, which knows the backend FFT type it is actually holding. Type definitions and/or C++11 aliases would remove the necessity to explicitly use the template parameter(s).

I'm not sure if this is good or already redundant with some of the stuff already going on in our templates. But I would (and will) start thinking along these lines.

typename T::value_type,
decltype(sin(std::declval<typename T::value_type>())),
decltype(cos(std::declval<typename T::value_type>()))
> > : std::true_type
Copy link
Collaborator

@cosurgi cosurgi Jun 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a very nice template. However I am a little bit concerned with it's name. Maybe better to be more explicit that it checks for presence of sin(…) and cos(…). Like maybe has_sin_cos<…> ?

Following this, I think we should to go through Boost libs and add tests for stuff which happens to have sin(…) and cos(…), this one is the first contender.

It's also possible to calculate the sin(…) and cos(…) of a matrix.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far we have two kinds of template algorithms:

  1. those that assume the type is complex so that we can use sin/cos to compute the roots of unity (so that we don't have to ask for the user for the roots and we don't have to use power functions to derive smaller roots from larger ones);
  2. and those who would work even in a larger domain (this domain include complex as well) of algebraic ring types.

The intention of this template is to tell if the argument represents a complex number, so that we can choose one DFT implementation or the other. Checking for sin/cos is a first approach to deduce the answer for unknown types. We could change the logic if needed to.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see. Then maybe simpler it would be to use the native boost::is_complex from #include <boost/type_traits/is_complex.hpp> ?

Copy link
Collaborator

@Lagrang3 Lagrang3 Jun 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be great! But the documentation says:
"If T is a complex number type then true (of type std::complex for some type U), otherwise false."
That means (I've just tested)
boost::is_complex<boost::multiprecision::cpp_complex_quad>::value
is false.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we could alias and specialize the template boost::is_complex inside the boost::math::fft namespace to evaluate to true all boost::multiprecision::cpp_complex<...> types. But then I guess boost::math would be dependent on boost::type_traits and boost::multiprecision.

Copy link
Collaborator

@cosurgi cosurgi Jun 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That means (I've just tested) boost::is_complex<boost::multiprecision::cpp_complex_quad>::value is false.

@ckormanyos , this seems like a bug to me, what would you say about this?

EDIT: or maybe there is some other is_complex template for checking multiprecision stuff, like multiprecision::is_complex?

Then boost::is_complex<…> or boost::multiprecision::is_complex<…> would do the trick :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the Type Traits library, that's not a bug
https://github.com/boostorg/type_traits/blob/develop/include/boost/type_traits/is_complex.hpp
The value is true if the type is std::complex<...> otherwise false. Hence the template tells if the type is "complex", where "complex" means std::complex.

Multiprecision has its own definition of the is_complex template
https://github.com/boostorg/multiprecision/blob/develop/include/boost/multiprecision/traits/is_complex.hpp
But it does the same thing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking this. We will have time figure this out. Maybe for example check if the type uses complex_adaptor<…>. But it's not urgent. What matters is that now I understand your intentions in this part of code :)

Copy link
Member Author

@ckormanyos ckormanyos Jun 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In complex_adaptor, we use things like

enable or test a Boolean value from:

boost::multiprecision::detail::is_complex<Result>::value

I need to check again if this works for non-built-in (i.e., multiprecision) types.

But here is a rather restricting constraint. Very much work was done to make Boost.Math standalone, requiring no other Boost library dependencies other than itself. It might be incongruent with the evolution of Boost.Math to have FFT depend on Boost.Multiprecision.

Copy link
Collaborator

@cosurgi cosurgi Jun 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. to have FFT depend on Boost.Multiprecision.

I definitely agree with that. We will need to do more SFINAE (or #ifdef :) ) tricks to make sure that a simple line that calls FFT on a simple double type does not pull extra dependencies with it.

void_t<
typename T::value_type,
decltype(sin(std::declval<typename T::value_type>())),
decltype(cos(std::declval<typename T::value_type>()))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. if I get the intentions correctly it should be checking for presence of

decltype(sin(std::declval<T>())),
decltype(cos(std::declval<T>()))

Or did I misunderstood something?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, you are computing sin(…) and cos(…) of phase ∈ ℝ. OK. I will need to ponder this for a while.

Copy link
Collaborator

@Lagrang3 Lagrang3 Jun 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is not so important to compute the sin/cos of the complex number. Our implementation needs the sin/cos of the underlying real phase.

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jun 28, 2021

We could replace the line:

boost::math::fft::dft_forward<boost::math::fft::bsl_dft>(...)

With the call to the static function forward in the template holder struct, which knows the backend FFT type it is actually holding. Type definitions and/or C++11 aliases would remove the necessity to explicitly use the template parameter(s).

I'm not sure if this is good or already redundant with some of the stuff already going on in our templates. But I would (and will) start thinking along these lines.

You mean we could provide the following interface, for example

boost::math::bsl_dft::forward(...)

to alias our current

boost::math::fft::dft_forward<boost::math::fft::bsl_dft>(...)

@ckormanyos
Copy link
Member Author

You mean we could provide the following interface, for example

I believe there are two fundamental methods, both potentially using aliases.

One method would amount to putting the template specializations of functions like forward in a namespace, whereby each individual backend type would have its own individual namespace.

A second method would be to place public static wrapper functions such as forward within a skinny wrapper struct that is instantiated for the FFT backend type. In this way, we could alias this particularly instantiated struct and eliminate the actual need to specifically mention the backend type as a template parameter to the function itself.

@Lagrang3
Copy link
Collaborator

OK. Then I understood correctly.

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 1, 2021

I've pushed the convolution implementation and Rader's algorithm for prime number sizes. Rader's implementation needs convolution routines to be implemented in O(n log n) for it to have O(n log n) complexity as well. The convolution that I uploaded will rescale the input into a power of two size array in order to profit from the most efficient of our FFTs.

Before this commit, the composite size FFT had complexity O(n (p1 + p2 + p3 + ...)) complexity where the pi's are the prime factors of n. That means that for example n = p1 p2, the multiplication of two big prime numbers, we are very inneficient.
However, now that we have Rader's algorithm, the composite number FFT will also have O(n log n) complexity.

The code is a bit messy and I have used naive discrete mathematics, but at least it works with the tests.

@ckormanyos
Copy link
Member Author

This is moving along well. Good job @Lagrang3

Somewhere in your commit, I saw something that might look like the pwoer-modulus function. I often prefer to use the so-called ladder method for pow-mod function. i can contribute one if you like?

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 1, 2021

This is moving along well. Good job @Lagrang3

Somewhere in your commit, I saw something that might look like the pwoer-modulus function. I often prefer to use the so-called ladder method for pow-mod function. i can contribute one if you like?

Show us the ladder method. I never heard of it. Thank you. By the way, I was thinking of writing a custom type for modular arithmetics and then re-use template power function.

@ckormanyos
Copy link
Member Author

the ladder method

If you look in one of my other projects, there is a power-modulus function for wide integer types here.

That particular template uses additional parameters, but after about this line here, the code is generic for integral type(s).

This subroutine calculates (b^p) % m

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 1, 2021

the ladder method

If you look in one of my other projects, there is a power-modulus function for wide integer types here.

That particular template uses additional parameters, but after about this line here, the code is generic for integral type(s).

This subroutine calculates (b^p) % m

Great, thanks! I'll have a look.

@ckormanyos
Copy link
Member Author

Nice work!

@Lagrang3 and @cosurgi it seems like at least some of the generic FFT interface work is actually done.

A clerical issue...?

Does it make sense to merge this to the main branch of the fork and either close this issue and raise new issues?
Or should we keep this PR open.

To me it yould seem nice to get all of the prime sizes work, etc. onto the main branch of the fork so that new branches automatically get it.

Ideas on this clerical issue?

@ckormanyos
Copy link
Member Author

... Or merge the PR, but keep the branch open if necessary for potential later PR? Or just keep the PR open?

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 4, 2021

@ckormanyos Keep this one open. The generic vs complex interface still needs refinement.
Furthermore, to me this PR could be my main branch. For example, when I start working on the allocator template in another branch I could PR the allocator branch into this one.

@ckormanyos
Copy link
Member Author

Keep this one open. The generic vs complex interface still needs refinement.
Furthermore, to me this PR could be my main branch. For example, when I start working on the allocator template in another branch I could PR the allocator branch into this one.

Understood. Thanks for clarification @Lagrang3.

So when I get a little time slot to add some more compilers/OS-es in CI, then I'll do it preferably here where the action is going on then...

@cosurgi
Copy link
Collaborator

cosurgi commented Jul 4, 2021

If we now work on this branch instead, then perhaps it makes sense to put here some commits from other branch, to avoid merge conflicts in the future. For example this commit seems to be omitted: 2162cac, there could be more though.

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 4, 2021

If we now work on this branch instead, then perhaps it makes sense to put here some commits from other branch, to avoid merge conflicts in the future. For example this commit seems to be omitted: 2162cac, there could be more though.

Sure. Feel free to merge fft_chris and fft_janek here.

@ckormanyos
Copy link
Member Author

ckormanyos commented Jul 4, 2021

There is kind of a flow for this kind of thing.

A branch with a very long, lively length of existence is inherently a bit dangerous. Usually we merge branches periodically to the main branch. Others on branches subsequently merge develop into their local branches prior to making PRs (PRs which are then dealt with in a timely fashion). And the whole development flow moves along in a coherent, coordinated fashion.

It might actually make sense to figure out what should be merged to main, what else and then actually coherently merge these things.

Basically, I feel uneasy when the integrity and tracked nature of the develop branch is lost in favor of feature branches. We might want to re-establish develop in the next week or so...

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 4, 2021

I have little experience on this matters. But to my opinion, having multiple branches has the scope of putting many people working coherently in the same project. As a single person, I can hardly work on two features at the same time. Therefore, I prefer having my own master branch, eduardo_master for example, with decent working code. Then I could also have sub-branches (one at the time) derived from the eduardo_master where I implement the new features step by step. Once the feature branch arrives to a good point I would merge it into eduardo_master. In fact, I have been working like that while pushing to generic_fft_interface, but you don't see those feature branches because they're only local to my computer.

I agree that once this PR has fulfilled its purpose, it can be merged into develop. But right now I think that the "generic fft interface" vs "complex fft" needs to be reviewed. There are pending issues in the discussion above that are critical to this branch purpose. For example: how to decide when a type is good to do complex FFT or not. The other day by mistake I typed a test with a non-complex type with the complex FFT interface, and the code compiled. That shouldn't happen and should be looked into before we declared the work done.

Right now I am in the mood to look into the allocator stuff. Hence I might use the last commit here as starting point for another branch to work on those.

@ckormanyos
Copy link
Member Author

Good job on all this so far. It's really moving forward.

might use the last commit here as starting point for another branch

That above makes sense although I must admit, I'm not a huge fan of branching from branches. But you know different people have different ways of working. So please find your own preference. We have just a small number of developers in this project right now so we can keep most stuff straight however we decide to move forward.

I think it might make sense reather sooner than later to call one of these branches or a combination of a few of them to be, let's say, not complete, but representative of the best state of the work so far. At such times in the project progress, merges to develop really could make sense. The "generic fft interface" branch can stay active and another PR can be raised immediately, but it might make sense to get some of the really good results you already have into develop on the fork.

In this way, develop of our fork could sort of play the role of the best state of this project, passing CI, etc. and keep moving forward. Basically, I'd recommend every week or so, certainly after 2 or 3 weeks, to push develop forward. But stick with your preference if there is a strong one.

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 5, 2021

I think it might make sense reather sooner than later to call one of these branches or a combination of a few of them to be, let's say, not complete, but representative of the best state of the work so far. At such times in the project progress, merges to develop really could make sense. The "generic fft interface" branch can stay active and another PR can be raised immediately, but it might make sense to get some of the really good results you already have into develop on the fork.

In this way, develop of our fork could sort of play the role of the best state of this project, passing CI, etc. and keep moving forward. Basically, I'd recommend every week or so, certainly after 2 or 3 weeks, to push develop forward. But stick with your preference if there is a strong one.

That said from your part. I must add: the state of this branch is not final but it is a good result nonetheless.
I have no inconvenience in merging this into develop.
Any further improvement can be done starting from there.

Please proceed.

@ckormanyos ckormanyos merged commit e8b3269 into develop Jul 5, 2021
@ckormanyos
Copy link
Member Author

ckormanyos commented Jul 5, 2021

synchronized the PR via push to master

@ckormanyos
Copy link
Member Author

further improvement can be done starting from there.

Yes.

Please proceed.

Done. Merged to develop in the fork. I also synchronized the fft_chris branch to develop and resoved merge conflicts there. So now I can work in, let's say, my branch with all this cool new stuff and proceed with CI extensions.

You can push onto the generic_fft_interface branch and create a new PR from there. The history is less linear. That is, in fact, a downside. But the concept of having a branch open for weeks on end with no tangible progress visible on develop is, in my opinion, worth taking that downside into consideration.

Also, I tend to recommend this form of operation util a project gets a bit more stable. If a project has released already, has a stable stand, then open PRs for weeks on end are a different (and OK) story. I hope some of this makes sense.

Thanks.

@Lagrang3
Copy link
Collaborator

Lagrang3 commented Jul 5, 2021

Hurray!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants